﻿using System;
using System.ComponentModel;
using System.Globalization;
using System.Xml.Linq;

namespace Silverlight
{
    /// <summary>
    /// DataField state
    /// </summary>
    public enum DataFieldState
    {
        Modified,
        Unchanged
    }

    /// <summary>
    /// DataField version
    /// </summary>
    public enum DataFieldVersion
    {
        Current,
        Original,
        Proposed
    }

    /// <summary>
    /// Provides DataField functionality
    /// </summary>
    public class DataField
    {
        private const string NonValidDataType = "\"{0}\" is not valid value for \"{1}\" data type";
        private const string UnrecognizedDataType = "Unrecognized data type: ";

        #region Data Fields
        /// <summary>
        /// "CurrentValue" property data field
        /// </summary>
        private string _currentValue;

        /// <summary>
        /// "FieldError" property data field
        /// </summary>
        private string _fieldError;

        /// <summary>
        /// "FieldState" property data field
        /// </summary>
        private DataFieldState _fieldState;

        /// <summary>
        /// "FieldVersion" property data field
        /// </summary>
        private DataFieldVersion _fieldVersion;

        /// <summary>
        /// Holds field version before it became Proposed
        /// </summary>
        private DataFieldVersion _fieldVersionBeforeProposed;

        /// <summary>
        /// "HasChanges" property data field
        /// </summary>
        private bool _hasChanges;

        /// <summary>
        /// "HasError" property data field
        /// </summary>
        private bool _hasError;

        /// <summary>
        /// "Index" property data field
        /// </summary>
        private int _index;

        /// <summary>
        /// "InEditMode" property data field
        /// </summary>
        private bool _inEditMode;

        /// <summary>
        /// "OriginalValue" property data field
        /// </summary>
        private string _originalValue;

        /// <summary>
        /// "ProposedValue" property data field
        /// </summary>
        private string _proposedValue;

        /// <summary>
        /// "Row" property data field
        /// </summary>
        private DataRow _row;
        #endregion

        #region Events
        /// <summary>
        /// Is raised before data was changed
        /// </summary>
        internal event EventHandler<DataSetEventArgs> FieldValueChanging;

        /// <summary>
        /// INotifyPropertyChanged interface implementation
        /// </summary>
        internal event PropertyChangedEventHandler PropertyChanged;
        #endregion

        #region Constructors
        /// <summary>
        /// Default constructor
        /// </summary>
        internal DataField()
        {
            this._fieldState = DataFieldState.Unchanged;
            this._fieldVersion = DataFieldVersion.Original;
            this._fieldVersionBeforeProposed = DataFieldVersion.Original;
        }

        /// <summary>
        /// Makes DataField instance
        /// </summary>
        /// <param name="index">Column index</param>
        /// <param name="row">Data row</param>
        internal DataField(int index, DataRow row) : this()
        {
            // Validate arguments
            if (index < 0)
            {
                throw new ArgumentOutOfRangeException("index");
            }

            if (row == null)
            {
                throw new ArgumentNullException("row");
            }

            this._index = index;
            this._row = row;
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets column name
        /// </summary>
        internal string ColumnName
        {
            get
            {
                return this._row.Table.Columns[this._index].ColumnName;
            }
        }

        /// <summary>
        /// Gets current value
        /// </summary>
        internal string CurrentValue
        {
            get
            {
                return this._currentValue;
            }
        }

        /// <summary>
        /// Gets field error
        /// </summary>
        internal string FieldError
        {
            get
            {
                return this._fieldError;
            }
        }

        /// <summary>
        /// Gets "HasError" flag value
        /// </summary>
        internal bool HasError
        {
            get
            {
                return this._hasError;
            }
        }

        /// <summary>
        /// Gets DataField index
        /// </summary>
        internal int Index
        {
            get
            {
                return this._index;
            }
        }

        /// <summary>
        /// Gets original value
        /// </summary>
        internal string OriginalValue
        {
            get
            {
                return this._originalValue;
            }
        }

        /// <summary>
        /// Gets proposed value
        /// </summary>
        internal string ProposedValue
        {
            get
            {
                return this._proposedValue;
            }
        }

        /// <summary>
        /// Gets or sets field value
        /// </summary>
        internal string Value
        {
            get
            {
                switch (this._fieldVersion)
                {
                    case DataFieldVersion.Current:
                        return this._currentValue;
                    case DataFieldVersion.Original:
                        return this._originalValue;
                    case DataFieldVersion.Proposed:
                        return this._proposedValue;
                }

                throw new InvalidOperationException("Unrecognized field version");
            }
            set
            {
                // Do nothing if value is the same in original row
                if (this._fieldVersion == DataFieldVersion.Original && this._currentValue == value)
                {
                    return;
                }

                this.BeginEdit();

                // Set proposed value
                this._proposedValue = value;

                // Validate data type
							try{
                if(Convert.ChangeType(value, this._row.Table.Columns[this._index].DataType,null) == null)
									throw new InvalidCastException("["+value+"] can not be casted to "+this._row.Table.Columns[this._index].DataType.Name);
                    this.SetFieldError(null);
							}catch(InvalidCastException){
                    this.SetFieldError(String.Format(CultureInfo.CurrentCulture, NonValidDataType, value, this._row.Table.Columns[this._index].DataType.FullName));
                }

                // Raise Changing event
                if (FieldValueChanging != null)
                {
                    DataSetEventArgs dataSetEventArgs = new DataSetEventArgs(this._proposedValue);
                    FieldValueChanging(this, dataSetEventArgs);
                }

                this.OnPropertyChanged(this.ColumnName);
            }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Commits all the changes made since the last time AcceptChanges was called
        /// </summary>
        internal void AcceptChanges()
        {

            this.EndEdit();
            if (this._hasChanges)
            {
                this._originalValue = this._currentValue;
                this._proposedValue = null;
                this._hasChanges = false;
                this._fieldState = DataFieldState.Unchanged;
                this._fieldVersion = DataFieldVersion.Original;
            }
        }

        /// <summary>
        /// Starts edit operation
        /// </summary>
        internal void BeginEdit()
        {
            if (!this._inEditMode)
            {
                // Put field in edit mode
                this._inEditMode = true;
                this._hasChanges = true;

                // Cache field verion
                this._fieldVersionBeforeProposed = this._fieldVersion;

                // Set field verion to Proposed
                this._fieldVersion = DataFieldVersion.Proposed;
            }
        }

        /// <summary>
        /// Cancels edit operation
        /// </summary>
        internal void CancelEdit()
        {
            if (this._inEditMode)
            {
                // Cancel editing
                this._inEditMode = false;

                // Clear proposed value
                this._proposedValue = null;

                // Restore field version from cache
                this._fieldVersion = this._fieldVersionBeforeProposed;
            }
        }

        /// <summary>
        /// Changes data type. Value is set to null if it's not compatible with new type
        /// </summary>
        /// <param name="proposedValue">Data type name</param>
        internal void ChangeType(string proposedValue)
        {
            if (!DataField.ValidateDataType(this._currentValue, proposedValue))
            {
                this._currentValue = null;
            }

            if (!DataField.ValidateDataType(this._originalValue, proposedValue))
            {
                this._originalValue = null;
            }

            if (!DataField.ValidateDataType(this._proposedValue, proposedValue))
            {
                this._proposedValue = null;
            }
        }

        /// <summary>
        /// Copies DataFeild
        /// </summary>
        /// <param name="dataField">Target DataField</param>
        internal void Copy(DataField dataField)
        {
            dataField._currentValue = this._currentValue;
            dataField._fieldError = this._fieldError;
            dataField._fieldState = this._fieldState;
            dataField._fieldVersion = this._fieldVersion;
            dataField._fieldVersionBeforeProposed = this._fieldVersionBeforeProposed;
            dataField._hasChanges = this._hasChanges;
            dataField._hasError = this._hasError;
            dataField._inEditMode = this._inEditMode;
            dataField._originalValue = this._originalValue;
            dataField._proposedValue = this._proposedValue;
        }

        /// <summary>
        /// Ends edit operation
        /// </summary>
        internal void EndEdit()
        {
            if (this._inEditMode)
            {
                this._currentValue = this._proposedValue;
                this._inEditMode = false;
                this._fieldState = DataFieldState.Modified;
                this._fieldVersion = DataFieldVersion.Current;
                this._hasChanges = true;
                this.OnPropertyChanged(this.ColumnName);
            }
        }

        /// <summary>
        /// Raises PropertChanged event
        /// </summary>
        /// <param name="propertyName">Property name</param>
        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        /// <summary>
        /// Rejects all changes made since AcceptChanges was last called
        /// </summary>
        internal void RejectChanges()
        {
            this.CancelEdit();
            if (this._hasChanges)
            {
                // Validate data type
                if (DataField.ValidateDataType(this._originalValue, this._row.Table.Columns[this._index].DataType.FullName))
                {
                    this.SetFieldError(null);
                }
                else
                {
                    this.SetFieldError(String.Format(CultureInfo.CurrentCulture, NonValidDataType, this._originalValue, this._row.Table.Columns[this._index].DataType.FullName));
                }

                this._currentValue = this._originalValue;
                this._proposedValue = null;
                this._hasChanges = false;
                this._fieldState = DataFieldState.Unchanged;
                this._fieldVersion = DataFieldVersion.Original;
                this.OnPropertyChanged(this.ColumnName);
            }
        }

        /// <summary>
        /// Sets field error
        /// </summary>
        /// <param name="errorMessage">Error message</param>
        private void SetFieldError(string errorMessage)
        {
            this._fieldError = errorMessage;
            if (string.IsNullOrEmpty(errorMessage))
            {
                if (this._hasError)
                {
                    this._hasError = false;
                    OnPropertyChanged(this.ColumnName + "HasError");
                    OnPropertyChanged(this.ColumnName + "Error");
                }
                else
                {
                    this._hasError = false;
                }
            }
            else
            {
                if (!this._hasError)
                {
                    this._hasError = true;
                    OnPropertyChanged(this.ColumnName + "HasError");
                    OnPropertyChanged(this.ColumnName + "Error");
                }
                else
                {
                    this._hasError = true;
                }
            }
        }

        /// <summary>
        /// Validates data type
        /// </summary>
        /// <param name="value">Value for validation</param>
        /// <param name="dataTypeFullName">Data type full name</param>
        /// <returns>True if value is of valid data type, otherwise false</returns>
        private static bool ValidateDataType(string value, string dataTypeFullName)
        {
            // Null is a valid value
            if (value == null)
            {
                return true;
            }

            switch (dataTypeFullName)
            {
                case "System.Boolean":
                    bool boolValue;
                    if (bool.TryParse(value, out boolValue))
                    {
                        return true;
                    }

                    break;
                case "System.DateTime":
                    DateTime dateTimeValue;
                    if (DateTime.TryParse(value, out dateTimeValue))
                    {
                        return true;
                    }

                    break;
                case "System.Int32":
                    int intValue;
                    if (int.TryParse(value, out intValue))
                    {
                        return true;
                    }

                    break;
                case "System.Int64":
                    long longValue;
                    if (long.TryParse(value, out longValue))
                    {
                        return true;
                    }

                    break;
                case "System.String":
                    return true;
            }

            return false;
        }
        #endregion
    }
}
